/************************************************
 *** Datei: DroneNetController.cpp
 ***
 *** Erstellt am: 30.05.2012
 ***       Autor: Max Stark <Max.Stark88@googlemail.com>
 ***	 Version: 1.0
 ***
 *** Beschreibung:
 *** <hier Beschreibung einfuegen>
 ************************************************/

#include "DroneNetController.h"
#include "DroneSwarmController.h"

DroneNetController::DroneNetController(DroneSwarmController* dsController) {
	this->dsController = dsController;
}



/************************************************
 *** DRONE BEACON SEND/LISTEN
 ************************************************/

/*
 * Sendet in regelmaessigen Intervallen ein Drohnen-Beacon,
 * das von den anderen Drohnen im Drohnennetz empfangen werden kann
 * Inhalt der Nachricht ist der Rechnername und der Name des
 * Drohnen Modells
 */
void DroneNetController::sendDroneBeacon() {
	io_service 		ioService;
	udp::socket 	socket(ioService);
	udp::endpoint 	destEndpoint(address_v4::from_string(DRONENET_IPADDRESS), DRONE_BEACON_PORT);
	this->openSocket(&socket, true, NULL);

	while(this->sendDroneBeaconThread_Active) {
		// Nachricht zusammenbasteln
		string mes = this->buildDroneMessage();
		// Nachricht senden
		try {
			socket.send_to(buffer(mes), destEndpoint);
		} catch (std::exception& e) {
			cerr << "error: " << e.what() << endl;
		}
		this_thread::sleep(boost::posix_time::milliseconds(DRONE_BEACON_INTERVAL));
	}

	this->closeSocket(&socket);
}

/*
 * Lauscht auf dem Drohnen-Beacon Socket. Wird ein Drohnen-Beacon empfangen
 * wird die Drohnen Liste aktualisiert
 */
void DroneNetController::listenDroneBeacons() {
	size_t size = 256;
	char recvBuf[size];

	io_service 		ioService;
	udp::socket 	socket(ioService);
	udp::endpoint	senderEndpoint;
	udp::endpoint 	localEndpoint(address_v4::any(), DRONE_BEACON_PORT);
	this->openSocket(&socket, true, &localEndpoint);

	while(true) {
		memset(recvBuf, 0, size);
		// Warten bis eine Nachricht empfangen wird
		try {
			socket.receive_from(buffer(recvBuf, size), senderEndpoint);
		} catch (std::exception& e) {
			cerr << e.what() << endl;
		}

		// Nachricht auswerten
		DroneListEntry drone = this->buildDroneListEntry(recvBuf,
				senderEndpoint.address().to_string());
		// In Liste eintragen, wenn nicht eigenes Beacon
		if(drone.strDroneHostName != host_name())
			this->dsController->insertIntoDroneList(drone);
	}

	this->closeSocket(&socket);
}





/****************************************************
 *** MASTERMESSAGE SEND/LISTEN
 ***************************************************/

/*
 * Sendet einen Master Request an die gewuenschte Drohne und aktualisiert
 * danach die Drohnen Liste.
 */
void DroneNetController::sendDroneMasterMessage(string ipAddress, string mes) {
	io_service 		ioService;
	udp::socket 	socket(ioService);
	udp::endpoint 	destEndpoint(address_v4::from_string(ipAddress), DRONE_MASTER_PORT);
	this->openSocket(&socket, true, NULL);

	// Master Message senden:
	try {
		socket.send_to(buffer(mes), destEndpoint);
	} catch (std::exception& e) {
		cerr << "error: " << e.what() << endl;
	}

	this->closeSocket(&socket);
}

/*
 *
 */
void DroneNetController::listenDroneMasterMessage() {
	size_t size = 256;
	char recvBuf[size];

	io_service 		ioService;
	udp::socket 	socket(ioService);
	udp::endpoint	senderEndpoint;
	udp::endpoint 	localEndpoint(address_v4::any(), DRONE_MASTER_PORT);
	this->openSocket(&socket, true, &localEndpoint);

	while(true) {
		memset(recvBuf, 0, size);
		// Warten bis eine Nachricht empfangen wird
		try {
			socket.receive_from(buffer(recvBuf, size), senderEndpoint);
		} catch (std::exception& e) {
			cerr << e.what() << endl;
		}

		// Nachricht auswerten
		string tmp = recvBuf;
		if(tmp == DRONE_MASTER_MES_REQ) {
			// Wenn Nachricht == REQ dann Drohne in Slave-Liste aufnehmen
			this->dsController->updateDroneList_Slave(senderEndpoint.address().to_string(), true);
			//cout << "MasterMessage REQ empfangen" << endl;
		} else if(tmp == DRONE_MASTER_MES_FIN) {
			// Wenn FIN dann Drohne aus Slave-Liste streichen
			this->dsController->updateDroneList_Slave(senderEndpoint.address().to_string(), false);
			//cout << "MasterMessage FIN empfangen" << endl;
		}
	}

	this->closeSocket(&socket);
}





/****************************************************
 *** DRONEDATA SEND/LISTEN
 ***************************************************/

/*
 *
 */
void DroneNetController::sendDroneSlaveData() {
	io_service 		ioService;
	udp::socket 	socket(ioService);
	udp::endpoint 	destEndpoint(address_v4::from_string(DRONENET_IPADDRESS), DRONE_SLAVEDATA_PORT);
	this->openSocket(&socket, true, NULL);

	while(this->sendDroneSlaveDataThread_Active) {
		// Den aktuellen Befehl der eigenen Drohne holen
		string mes = this->dsController->getDrone()->getDroneController()->getNextDroneCommand()->toString();

		// Nachricht per Broadcast senden (TODO: anstatt nur an die Slave-Drohnen? Erhoeht die Netzlast
		// verringert aber damit die Last des Senders = es muss nicht ueber die Liste iteriert und fuer jede
		// Slave-Drohne ein eigener Socket angelegt werden etc.
		try {
			socket.send_to(buffer(mes), destEndpoint);
		} catch (std::exception& e) {
			cerr << "error: " << e.what() << endl;
		}

		this_thread::sleep(boost::posix_time::milliseconds(DRONE_DATA_SEND_INTERVAL));
	}

	this->closeSocket(&socket);
}

/*
 *
 */
void DroneNetController::listenDroneMasterData() {
	size_t size = 256;
	char recvBuf[size];

	io_service 		ioService;
	udp::socket 	socket(ioService);
	udp::endpoint	senderEndpoint;
	udp::endpoint 	localEndpoint(address_v4::any(), DRONE_SLAVEDATA_PORT);
	this->openSocket(&socket, true, &localEndpoint);

	while(true) {
		memset(recvBuf, 0, size);
		// Warten bis Daten empfangen werden
		try {
			socket.receive_from(buffer(recvBuf, size), senderEndpoint);
		} catch (std::exception& e) {
			cerr << e.what() << endl;
		}

		// Pruefen ob Daten fuer diese Drohne bestimmt
		if(senderEndpoint.address().to_string() == this->dsController->actMasterDroneIPAddress) {
			string mes = recvBuf;
			//cout << "empfangene master-daten: " << mes << endl;

			DroneCommand* dc = new DroneCommand(mes);
			this->dsController->getDroneController()->setNextDroneCommand(dc);
		}
	}

	this->closeSocket(&socket);
}





/****************************************************
 *** Hilfsfkt.
 ***************************************************/

void DroneNetController::openSocket(udp::socket* socket, bool broadcast, udp::endpoint* localEndpoint) {
	try {
		socket->open(udp::v4());
		socket->set_option(udp::socket::reuse_address(true));
		socket->set_option(socket_base::broadcast(true));
		if(localEndpoint != NULL)
			socket->bind(*localEndpoint);
	} catch (std::exception& e) {
		cerr << "error: " << e.what() << endl;
	}
}

void DroneNetController::closeSocket(udp::socket* socket) {
	try {
		socket->close();
	} catch (std::exception& e) {
		cerr << "error: " << e.what() << endl;
	}
}

string DroneNetController::buildDroneMessage() {
	string mes("");

	// Computernamen
	mes.append(host_name()).append(":");
	// Vorhandene Drohne
	IDrone* drone = this->dsController->getDrone();
	if(drone != NULL) {
		if(drone->isConnected() == true) {
			mes.append(drone->getDroneModelName());
		} else {
			mes.append("Nicht mit Drohne verbunden");
		}
	} else {
		mes.append("Keine Drone vorhanden");
	}
	return mes;
}

DroneListEntry DroneNetController::buildDroneListEntry(char* recvBuf, string ipAddress) {
	DroneListEntry drone;

	int feldNr = 0;
	char* mes = strtok(recvBuf, ":");
	while(mes != NULL) {
		++feldNr;

		if(feldNr==1)
			drone.strDroneHostName = mes;
		else if(feldNr==2)
			drone.strDroneModelName = mes;
		else if(feldNr==3) {
			string tmp = recvBuf;
			if(tmp == DRONE_MASTER_MES_REQ) {
				drone.slaveDrone = true;
			} else { //if(mes == DRONE_MASTER_MES_FIN) {
				drone.slaveDrone = false;
			}
		}

		mes = strtok(NULL, ":");
	}

	// IP-Adresse
	drone.strIPAddress = ipAddress;

	return drone;
}





/****************************************************
 *** Methoden zur Steuerung des DroneNetControllers
 ***************************************************/

void DroneNetController::run() {
	this->sendDroneBeaconThread_Active = true;
	this->sendDroneBeaconThread = new thread(
			&DroneNetController::sendDroneBeacon, this);
	this->listenDroneBeaconsThread = new thread(
			&DroneNetController::listenDroneBeacons, this);

	this->listenDroneMasterMessageThread = new thread(
			&DroneNetController::listenDroneMasterMessage, this);

	this->sendDroneSlaveDataThread_Active = true;
	this->sendDroneSlaveDataThread = new thread(
			&DroneNetController::sendDroneSlaveData, this);

	this->listenDroneMasterDataThread = new thread(
			&DroneNetController::listenDroneMasterData, this);

	this->isRunning = true;
}

void DroneNetController::stop() {
	if(this->isRunning == true) {

		this->sendDroneBeaconThread_Active = false;
		this->sendDroneBeaconThread->join();
		//cout << "sendDroneBeaconThread terminiert" << endl;
		this->listenDroneBeaconsThread->detach();
		this->listenDroneBeaconsThread->join();
		//cout << "listenDroneBeaconsThread terminiert" << endl;

		this->listenDroneMasterMessageThread->detach();
		this->listenDroneMasterMessageThread->join();
		//cout << "listenDroneMasterDataThread terminiert" << endl;

		this->sendDroneSlaveDataThread_Active = false;
		this->sendDroneSlaveDataThread->join();
		//cout << "sendDroneSlaveDataThread terminiert" << endl;
		this->listenDroneMasterDataThread->detach();
		this->listenDroneMasterDataThread->join();
		//cout << "listenDroneMasterDataThread terminiert" << endl;

		delete this->sendDroneBeaconThread;
		delete this->listenDroneBeaconsThread;
		delete this->listenDroneMasterMessageThread;
		delete this->sendDroneSlaveDataThread;
		delete this->listenDroneMasterDataThread;

		this->isRunning = false;
	}
}

DroneNetController::~DroneNetController() {
	this->stop();
}

